#include "helper/object/std/StandardArray.h"
#include <sstream>
#include "helper/object/std/StandardRawType.h"
#include "helper/object/std/StandardString.h"
#include "helper/object/std/StandardPointer.h"
#include "helper/object/Hash.h"

namespace helper {
namespace object {

StandardArray::StandardArray(){
}
StandardArray::~StandardArray(){
    freeHolderResource();
}

size_t StandardArray::size() const{
    return array.size();
}


template<typename R, typename T, typename IsType> 
static std::pair<bool, R> getRaw(const StandardArray * array, const int index, const IsType & isType){
    auto * eptr = array->get(index);
    if(eptr != nullptr && isType(*eptr)){
        return std::make_pair(true, (R)(static_cast<T*>(*eptr)->get()));
    }
    return std::make_pair(false, R());
}
std::pair<bool, char> StandardArray::getByte(const int index) {
    return getRaw<char, Byte>(this, index, [](Object * obj){ return obj->isByte();});
}
std::pair<bool, int> StandardArray::getInt(const int index) {
    return getRaw<int, Integer>(this, index, [](Object * obj){ return obj->isInteger();});
}
std::pair<bool, long long> StandardArray::getLongLong(const int index) {
    return getRaw<long long, Integer>(this, index, [](Object * obj){ return obj->isInteger();});
}
std::pair<bool, double> StandardArray::getDouble(const int index) {
    return getRaw<double, Double>(this, index, [](Object * obj){ return obj->isDouble();});
}

std::pair<bool, std::string> StandardArray::getStdString(const int index){
    String * str = getString(index);
    return (str!= nullptr) ? std::make_pair(true, str->getValue()) : std::make_pair(false, "");
}

template<typename T, typename IsType> 
static T * getObject(StandardArray * array, const int index, const IsType & isType){
   auto * eptr = array->get(index);
   if(eptr != nullptr && isType(*eptr)){ // bool (Object*)
       return static_cast<T*>(*eptr);
   }
   return nullptr;
}
Hash * StandardArray::getHash(const int index) {
   return getObject<Hash>(this, index, [](Object *obj){ return obj->isHash(); });
}
Array * StandardArray::getArray(const int index) {
   return getObject<Array>(this, index, [](Object *obj){ return obj->isArray(); });
}
String * StandardArray::getString(const int index) {
    return getObject<String>(this, index, [](Object *obj){ return obj->isString(); });
}


template<typename T, typename IsType> 
static const T * getConstObject(const StandardArray * array, const int index, const IsType & isType){
   auto * eptr = array->get(index);
   if(eptr != nullptr && isType(*eptr)){ // bool (Object*)
       return static_cast<const T*>(*eptr);
   }
   return nullptr;
}
const Hash * StandardArray::getHash(const int index) const {
    return getConstObject<Hash>(this, index, [](Object*obj){ return obj->isHash();});
}
const Array * StandardArray::getArray(const int index) const {
    return getConstObject<Array>(this, index, [](Object*obj){ return obj->isArray();});
}
const String * StandardArray::getString(const int index) const {
    return getConstObject<String>(this, index, [](Object*obj){ return obj->isString();});
}
const Array::ValueType * StandardArray::get(const int index) const{
    if(index >= 0 && index < (int)size()){
        return &(array.at(index));
    }
    return nullptr;
}

std::pair<bool, void *> StandardArray::getPointer(const int index) {
    return getRaw<void *, Pointer>(this, index, [](Object * obj){ return obj->isPointer();});
}
int StandardArray::appendPointer(const void * ptr) {
    return append(new StandardPointer(ptr));
}

int StandardArray::append(char ch) {
    return append(new StandardByte(ch));
}
int StandardArray::append(int i) {
    return append(new StandardInteger(i));
}
int StandardArray::append(long long ll) {
    return append(new StandardInteger(ll));
}
int StandardArray::append(double dbl) {
   return append(new StandardDouble(dbl));
}
int StandardArray::append(const char * str) {
    return append(new StandardString(str));
}
int StandardArray::append(const char * buffer, size_t len) {
    return append(new StandardString(buffer, len));
}
int StandardArray::append(const char * buffer, size_t offset, size_t len) {
    return append(new StandardString(buffer, offset, len));
}
int StandardArray::append(size_t n, const char ch){
    return append(new StandardString(n, ch));
}
int StandardArray::append(const std::string & str){
    return append(new StandardString(str));
}
int StandardArray::append(const Array::ValueType & o){
    if(o != nullptr){
        array.push_back(o);
        return 0;
    }
    return -1;
}
void StandardArray::freeHolderResource(){
    for(auto iter = array.begin(); iter != array.end(); ++iter){
        auto ptr = *iter;
        if(ptr != nullptr){
            delete ptr;
            *iter = nullptr;
        }
    }
}

Array::ValueType StandardArray::remove(const int index){
    if(index >= 0 && index < (int)size()){
        Array::ValueType old = array.at(index);
        array.erase(array.begin() + index);
        return old;
    }
    return nullptr;
} 

Array::ValueType StandardArray::set(const int index, const ValueType & value){
    if(index >= 0 && index < (int)size()){
        Array::ValueType old = array.at(index);
        array[index] = value;
        return old;
    }
    return nullptr;
}

std::string StandardArray::toString() const{
    std::ostringstream oss;

    oss << "StandardArray:[" << array.size() << "] : ";
    for(auto iter = array.begin(); iter != array.end(); ++iter){
        std::string val = (*iter) != nullptr ? (*iter)->toString() : "nullptr";
        oss << "item:" <<  val << ", ";
    }
    return oss.str();
}

bool StandardArray::equals(const Object & obj) const {
    if(this == &obj) return true;

    if(obj.isArray()){
        auto & otherArray =  static_cast<const StandardArray&>(obj);
        if(size() != otherArray.size()) return false;

        auto iter = otherArray.iterator();
        for(auto & e : array){
            auto * cur = iter->next();
            if(e == nullptr || cur == nullptr || !e->equals(*cur)){
                return false;
            }
        }
        return true;
    }
    return false;
}
Object * StandardArray::copy() const {
    StandardArray * newArray =  new StandardArray();
    for(auto iter = array.begin(); iter != array.end(); ++iter) {
        if ((*iter) != nullptr) {
            newArray->append((*iter)->copy());
        }
        else {
            delete newArray;
            return nullptr;
        }
    }
    return newArray;
}

void StandardArray::clear() {
    freeHolderResource();
    array.clear();
}

std::shared_ptr<Iterator<Array::ValueType>> StandardArray::iterator() const{
    return std::make_shared<StandardArrayIterator>(*this);
}

StandardArrayIterator::StandardArrayIterator(Array & array)
: arrayRef(array)
, index(0)
{
}

StandardArrayIterator::StandardArrayIterator(const Array & array)
: arrayRef(array)
, index(0)
{
}
StandardArrayIterator::~StandardArrayIterator(){
}

bool StandardArrayIterator::hasNext() const{
    if(index >= 0 && index < (int)arrayRef.size()){
        return true;
    }
    return false;
}
const Array::ValueType & StandardArrayIterator::next(){
    if(index >= 0 && index < (int)arrayRef.size()){
        StandardArray & sa = (StandardArray &)arrayRef;
        return sa.array.at(index++);
    }
    return this->getNullObjRef();
}

} // end of namespace object
} // end of namespace helper
